package cn.tedu.charging.cost.service.impl;

import cn.tedu.charging.common.pojo.param.ChargingProcessParam;
import cn.tedu.charging.common.pojo.vo.ChargingProcessVO;
import cn.tedu.charging.cost.dao.repository.ChargingDataESRepository;
import cn.tedu.charging.cost.dao.repository.CostRuleRepository;
import cn.tedu.charging.cost.dao.repository.RedisCostRuleRepository;
import cn.tedu.charging.cost.pojo.ChargingData;
import cn.tedu.charging.cost.pojo.po.ChargingDataPO;
import cn.tedu.charging.cost.pojo.po.CostRulePO;
import cn.tedu.charging.cost.service.CostService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

@Service
@Slf4j
public class CostServiceImpl implements CostService {
    @Autowired
    private CostRuleRepository costRuleRepository;
    @Autowired
    private ChargingDataESRepository chargingDataESRepository;
    @Autowired
    private RedisCostRuleRepository redisCostRuleRepository;

    private Map<String, ChargingData> chargingDataMap= new HashMap<>();
    @Override
    public ChargingProcessVO calculateCost(ChargingProcessParam chargingProcessParam) {
        log.debug("获取充电数据，参数：{}",chargingProcessParam.getOrderNo());
        //ChargingData chargingData = getChargingData(chargingProcessParam.getOrderNo());
        ChargingDataPO chargingData = getChargingDataPOByOrderNoFromES(chargingProcessParam.getOrderNo(),
                chargingProcessParam.getUserId(),
                chargingProcessParam.getGunId());
        log.debug("获取充电数据，结果同步的次数为：{}",chargingData.getCount());
        log.debug("计算价格入参：{}",chargingProcessParam);
        // 获取当前时间
        Integer currentHour = getCurrentHour();
        log.debug("获取当前时间，结果：{}", currentHour);
        // 根据场站id、枪头类型、当前时间，获取对应的计价规则
        CostRulePO costRulePO = getCostRule(chargingProcessParam.getStationId(), chargingProcessParam.getGunType(), currentHour);
        log.debug("根据场站id、枪头类型、当前时间，获取对应的计价规则，结果：{}", costRulePO);
        // 计算价格 TODO
        if (costRulePO!=null){
            // 获取计价名称
            String name = costRulePO.getName();
            // 获取价格
            BigDecimal powerFee = costRulePO.getPowerFee();
            // 获取同步来的电费
            Float chargingCapacity = chargingProcessParam.getChargingCapacity();
            // 计算价格
            // 保存同步的数据,和计算的价格
            // 判断是否是第一次
            if (chargingData.isFirst()){
                // 第一次
                firstCost2ChargingData(chargingData,powerFee,chargingCapacity);
            }else {
                // 第N次
                lastCost2ChargingData(chargingData,powerFee,chargingCapacity);
            }
        }else {
            log.error("故障，计价规则为空，要进行系统告警，业务无法继续执行");
        }

        // 调用返回对象
        ChargingProcessVO chargingProcessVO = buildChargingProcessVO(chargingData);
        log.debug("出参获取此时该订单,同步的度数和费用,时长，结果：{}",chargingProcessVO);
        return chargingProcessVO;
    }


    /**
     *
     * 通过充电数据 构建 计价服务计算价格的出参
     * 出参要返回的字段和数据
     * @param chargingDataPO
     * @return
     */

    private ChargingProcessVO buildChargingProcessVO (ChargingDataPO chargingDataPO){
        ChargingProcessVO chargingProcessVO = new ChargingProcessVO();
        log.debug("构建返回对象，参数source：{}",chargingDataPO);
        BeanUtils.copyProperties(chargingDataPO,chargingProcessVO);
        log.debug("构建返回对象，参数source：{},结果target：{}",chargingDataPO,chargingProcessVO);
        // 设置共计花费时间
        Long startTime = chargingDataPO.getStartTime();
        // 获取当前时间
        Long endTime = System.currentTimeMillis();
        // 计算消耗时间
        Long costTime = endTime - startTime;
        // 了解使用 LocalDateTime 进行时间的转换
        //如何计算已经充了几个小时呢？
        //Date 不推荐使用  --> joda-time --> java.time LocalDateTime
        //LocalDateTime now  = LocalDateTime.now();
        //Duration between = Duration.between(, now);
        //between.toHours();//计算已经充了多少小时
        chargingProcessVO.setTotalTime(costTime);
        return chargingProcessVO;
    }

    private void firstCost2ChargingData(ChargingDataPO chargingData,BigDecimal powerFee,Float chargingCapacity){
        log.debug("第一次计算费用，参数：{},{}",powerFee,chargingCapacity);
        BigDecimal cost = getCost(chargingCapacity, powerFee);
        // 为下一次计算费用准备
        // 设置总花费
        chargingData.setTotal(cost);
        log.debug("第一次计算费用，结果：{}",cost);
        // 设置充电度数
        chargingData.setChargingCapacity(chargingCapacity);
        log.debug("第一次计算费用，充电度数结果：{},单次费用即总费用:{}",chargingData.getChargingCapacity(),cost);
        // 保存数据 到ES
        log.debug("第一次计算费用，保存数据到ES，参数：{}",chargingData);
        chargingDataESRepository.save(chargingData);
    }
    private void lastCost2ChargingData(ChargingDataPO chargingData,BigDecimal powerFee,Float chargingCapacity){
        log.debug("第N次计算费用，参数：{},{}",powerFee,chargingCapacity);
        // 获取上一次的充电度数
        //Float firstChargingCapacity = chargingData.getChargingCapacity();
        // 获取当前真是的充电度数
        // = 同步的充电度数 - 上一次的充电度数
        //Float currentChargingCapacity = chargingCapacity - firstChargingCapacity;
        Float currentChargingCapacity = chargingData.getChargingCapacity(chargingCapacity);
        log.debug("第N次计算费用，当前充电度数，结果：{}",currentChargingCapacity);
        // 计算费用
        BigDecimal cost = getCost(currentChargingCapacity, powerFee);
        log.debug("第N次计算费用，单次费用，结果：{}",cost);
        // 设置总花费
        //BigDecimal total = chargingData.getTotal().add(cost);
        //log.debug("第N次计算费用，总费用，结果：{}",total);
        // 修改总花费
        chargingData.setTotal(cost);
        // 修改同步的充电度数
        chargingData.setChargingCapacity(chargingCapacity);
        log.debug("第N次计算费用，单次费用结果：{},充电度数结果：{},总费用，结果：{}",cost,chargingData.getChargingCapacity(),chargingData.getTotal());
        // 保存数据 到ES
        log.debug("第{}次计算费用，保存数据到ES，参数：{}",chargingData.getCount(),chargingData);
        chargingDataESRepository.save(chargingData);
    }



    private ChargingDataPO getChargingDataPOByOrderNoFromES(String orderNo,Integer userId,Integer gunId) {
        Optional<ChargingDataPO> optional = chargingDataESRepository.findById(orderNo);

        if (optional.isPresent()){
            ChargingDataPO chargingDataPO = optional.get();
            // 不为第一次 同步数据
            Integer count = chargingDataPO.getCount();
            chargingDataPO.setCount(count+1);

            // 保存消息
            log.debug("第{}次同步充电数据，修改后的数据保存到ES:{}",count,chargingDataPO);
            chargingDataESRepository.save(chargingDataPO);
            log.debug("第{}次同步充电数据，修改后的数据保存到ES 成功:{}",count,chargingDataPO);
            return chargingDataPO;
        }else {
            // 为第一次 同步数据
            ChargingDataPO chargingDataPO = new ChargingDataPO();
            // 设置订单号 为ID
            chargingDataPO.setId(orderNo);
            chargingDataPO.setCount(1);
            chargingDataPO.setGunId(gunId);
            // 设置开始充电时间 此时第一次调用 设置当前时间为订单开始时间
            chargingDataPO.setStartTime(System.currentTimeMillis());
            chargingDataPO.setOrderNo(orderNo);
            chargingDataPO.setUserId(userId);
            chargingDataPO.setTotal(new BigDecimal(0));
            chargingDataPO.setChargingCapacity(chargingDataPO.getChargingCapacity(0F));
            // 保存数据
            log.debug("第一次同步充电数据，保存数据到ES:{}",chargingDataPO);
            chargingDataESRepository.save(chargingDataPO);
            log.debug("第一次同步充电数据，保存数据到ES:{}",chargingDataPO);
            return chargingDataPO;
        }

    }

    /**
     * 获取充电数据
     * @param orderNo
     * @return
     */
    private ChargingData getChargingData(String orderNo){
        // 判断是否是第一次
        ChargingData chargingData = chargingDataMap.get(orderNo);
        if (chargingData==null){
            chargingData = new ChargingData();
            chargingData.setCount(1);
            chargingDataMap.put(orderNo,chargingData);
        }else {
            // 计算次数 数据value已经被修改了，set的时候
            chargingData.setCount(chargingData.getCount()+1);
        }
        return chargingData;
    }


    /**
     * 计算价格
     * @param chargingCapacity
     * @param powerFee
     * @return
     */
    private BigDecimal getCost(Float chargingCapacity,BigDecimal powerFee){
        // 类型转换 通过构造器
        //BigDecimal bigDecimal = new BigDecimal(chargingCapacity);
        // 1.2 转换为BigDecimal类型 通过方法valueOf(入参数)
        BigDecimal bigDecimal = BigDecimal.valueOf(chargingCapacity);
        BigDecimal multiply = bigDecimal.multiply(powerFee);

        return multiply;
    }


    /**
     * 获取当前时间
     * @return
     */
    private Integer getCurrentHour(){
        LocalDateTime now = LocalDateTime.now();
        return now.getHour();
    }

    /**
     * 根据场站id、枪头类型、当前时间，获取对应的计价规则
     * @param stationId
     * @param gunType
     * @param hour
     * @return
     */
    private CostRulePO getCostRule(Integer stationId,Integer gunType,Integer hour){
        log.debug("获取计价规则，参数：{},{},{}", stationId, gunType, hour);
        List<CostRulePO> costRules= getCostRule(stationId);
        log.debug("获取计价规则，结果：{}", costRules);
        if (!CollectionUtils.isEmpty(costRules)){
            for (CostRulePO costRule : costRules) {
                Integer costRuleGunType = costRule.getGunType();
                if (costRuleGunType==gunType){
                    Integer startTime = costRule.getStartTime();
                    Integer endTime = costRule.getEndTime();
                    if (hour>=startTime && hour<=endTime){
                        log.debug("获取计价规则，结果：{}", costRule);
                        return costRule;
                    }
                }
            }
        }
        return null;
    }

    // 获取所有的计价规则 通过stationId 场站id
    private List<CostRulePO> getCostRule(Integer stationId){
    	log.debug("获取所有的计价规则，参数：{}", stationId);
        // 调用redis 查询 如果有缓存，直接返回缓存数据 没有缓存，调用数据库查询（保存到redis）
        log.debug("获取所有的计价规则，从redis 中查询，参数：{}", stationId);
        List<CostRulePO> cacheCostRules= redisCostRuleRepository.getCostRuleFromRedis(stationId);
        log.debug("获取所有的计价规则，结果：{}", cacheCostRules);
        if (!CollectionUtils.isEmpty(cacheCostRules)){
            log.debug("获取所有的计价规则，结果：{}", cacheCostRules);
            return cacheCostRules;
        }else {
            log.debug("redis 没有数据，从数据库中获取");
            List<CostRulePO> dbCostRules= costRuleRepository.getCostRuleByStationId(stationId);
            log.debug("从数据库中获取的计价规则，结果：{}", dbCostRules);
            if (!CollectionUtils.isEmpty(dbCostRules)){
                // 保存到redis
                redisCostRuleRepository.saveCostRule2Redis(stationId,dbCostRules);
                return dbCostRules;
            }
            log.debug("从数据库中获取的计价规则,可能忘记录入计价规则，结果为空");
        }
        // 返回空的集合 使用工具类提供的空集合 每次不会创建一个新的集合
        return Collections.emptyList();
    }

}
